﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;

namespace Perceiveit.Xml
{
    public abstract class XKeyedCollection<TKey,TItem> : 
        XEntityCollectionBase, IEnumerable<TItem>
        where TItem : XEntity
    {
        #region ivars

        private Dictionary<TKey, int> _keyoffsets;
        private List<TItem> _cache;
        private XFactory<TItem> _factory;
        private bool _populated;

        #endregion

        /// <summary>
        /// Identifies if this collection has been populated 
        /// </summary>
        protected bool IsPopulated { get { return _populated; } }

        /// <summary>
        /// Gets the factory for this keyed collections' child elements
        /// </summary>
        protected XFactory<TItem> Factory { get { return _factory; } }

        /// <summary>
        /// Gets the mapping dictionary between item keys and their offsets in the cache
        /// </summary>
        protected Dictionary<TKey, int> KeyOffsets { get { return _keyoffsets; } }

        /// <summary>
        /// Gets the list of items that have been cached in this KeyedCollection
        /// </summary>
        protected List<TItem> Cache { get { return _cache; } }

        /// <summary>
        /// Gets the number of items in this dictionary
        /// </summary>
        public int Count
        {
            get
            {
                this.EnsurePopulated();
                return this._cache.Count;
            }
        }

        /// <summary>
        /// Identifies if this collection is read only 
        /// </summary>
        public virtual bool IsReadOnly { get { return true; } }

        /// <summary>
        /// Gets the TItem for the specified key
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public TItem this[TKey key]
        {
            get
            {
                this.EnsurePopulated();
                int offset;
                if (this._keyoffsets.TryGetValue(key, out offset))
                    return this._cache[offset];
                else
                    return null;
            }
        }


        public XKeyedCollection(XFactory<TItem> factory)
        {
            if (null == factory)
                throw new ArgumentNullException("factory");

            _factory = factory;
            _populated = false;
            _keyoffsets = null;
            _cache = null;
        }

        public void NotifyKeyChange(TItem item)
        {
            this.EnsurePopulated();
            int index = _cache.IndexOf(item);
            if (index > -1)
            {
                TKey origkey = default(TKey);
                bool found = false;

                foreach (KeyValuePair<TKey, int> kvp in this._keyoffsets)
                {
                    if (kvp.Value == index)
                    {
                        origkey = kvp.Key;
                        found = true;
                        break;
                    }
                }
                if (found)
                {
                    _keyoffsets.Remove(origkey);
                    _keyoffsets.Add(this.GetKeyForItem(item), index);
                }
            }
        }


        protected override void OnInitComplete()
        {
            base.OnInitComplete();
            this.ResetCollection();
        }


        protected virtual void ResetCollection()
        {
            if (this.IsPopulated)
            {
                foreach (TItem item in this.Cache)
                {
                    item.Element.Remove();
                }
                this.Cache.Clear();
            }
            this._cache = null;
            this._populated = false;
            this._keyoffsets = null;
            this.RegisterCollectionChanged();
        }

        /// <summary>
        /// 
        /// </summary>
        protected void EnsurePopulated()
        {
            if (!this._populated)
            {
                this._cache = new List<TItem>();
                this._keyoffsets = new Dictionary<TKey, int>();

                if (this.HasExistingNode)
                {
                    XFactory<TItem> factory;
                    foreach (XElement ele in this.Element.Elements())
                    {
                        if (this.Factory.IsKnownName(ele.Name, out factory))
                        {
                            TItem item = factory.GetEntityForName(ele.Name);
                            item.Init(ele);
                            this._cache.Add(item);
                            TKey key = this.GetKeyForItem(item);
                            this._keyoffsets.Add(key, this._cache.Count - 1);
                        }
                    }
                }
                this._populated = true;
            }
        }

        //
        // abstract methods
        //


        protected abstract TKey GetKeyForItem(TItem item);

        //
        // interface implementations
        //

        #region IEnumerable<TItem> Members

        public IEnumerator<TItem> GetEnumerator()
        {
            this.EnsurePopulated();
            return this._cache.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        #endregion
    }
}
